/*
 * con_command.cpp
 *
 *  Created on: Sep 20, 2010
 *      Author: khanton
 */

#include <iostream>
#include <iomanip>
#include <sstream>
#include <time.h>

#include "con_command.h"
#include "log.h"
#include "format.h"
#include "con_command.h"
#include "controllers_network.h"
#include "con_state.h"
#include "options.h"

const unsigned char PULT_ID = 0x50;
const unsigned char CONTROLLER_ID = 0x2A;

#define CONTROLLER_ID(a, b) (((b << 8) & 0xff00) | (a & 0xff))

con_command::con_command(controllers_network &_network, sqlhandle &_sqlhandle):
	network_(_network),
	sqlhandle_(_sqlhandle)
{
}

con_command::~con_command()
{
}

unsigned char con_command::calc_cs(const unsigned char *_buffer, size_t _len)
{
	unsigned char c = 0;

	for(size_t i = 0; i < _len; i++)
	{
		c += _buffer[i];
	}

	return 0 - c;
}

void con_command::build_packet(unsigned char *_dst, const unsigned char *_src, size_t _len, size_t _buff_len)
{
	if (_len + 4 > _buff_len)
	{
		throw std::length_error("Buffer too small!");
	}

	_dst[0] = 0x10;
	_dst[1] = _len;

	memcpy(_dst + 2, _src, _len);

	_dst[_len + 2] = 0x10;
	_dst[_len + 2 + 1] = calc_cs(_dst + 1, _len + 1);
}

bool con_command::parse_packet(const unsigned char *_src, unsigned int _src_len,  const unsigned char **_dst, unsigned int &_dst_len)
{
    unsigned int pos = 0;

    if (_src[pos] != PSTART)
    {
  		g_log.msg(MSG_NORMAL, "Parse packet: No start sign.");
        return false;
    }    

    pos++;

    unsigned int len = _src[pos];     // data length

    if ((len + 2) > _src_len)
    {
  		g_log.msg(MSG_NORMAL, "Parse packet: Packet too small.");
    	return false;
    }	

    pos += len + 1;

    if (_src[pos] != PSTART)
    {
  		g_log.msg(MSG_NORMAL, "Parse packet:No KS sign.");
        return false;
    }    

    pos++;

    if (_src[pos] != calc_cs(_src + 1, len + 1))
    {
  		g_log.msg(MSG_NORMAL, "Parse packet: Wrong KS.");
        return false;
    }    

    *_dst = (_src + 2);
    _dst_len = len;

    return true;
}


std::string con_command::dump_packet(const unsigned char *_src, unsigned int _src_len)
{
    std::stringstream ss;
    
    ss << std::setbase(16); 
    
    for(unsigned int i = 0; i < _src_len; i++)
    {
	  if (i > 0)
	  {
		ss << " ";
	  }
	  
	  ss << std::setfill('0') << std::setw(2) << (int) (_src[i]);
    }
    
    return ss.str();
}

//////////////////////////////////////////////////////////////////////
// hello_command
//////////////////////////////////////////////////////////////////////


bool hello_command::create_command(std::vector<boost::asio::const_buffer> &_out_buffers, con_connection_ptr _connection)
{
	return false;
}

bool hello_command::need_read()
{
	return pult_;
}

con_command *hello_command::get_next_command()
{
	if (pult_)
	{
		return new pult_command(network_, sqlhandle_);
	}
	else
	{
		return new get_status_command(network_, sqlhandle_, controller_id_);
	}
}

hello_command::hello_command(controllers_network & _network, sqlhandle &_sqlhandle):
	con_command::con_command(_network, _sqlhandle)
{
}

hello_command::~hello_command()
{
}

con_command::return_t hello_command::process_request(boost::asio::const_buffer _in_buffer, size_t _bytes_transferred, std::vector<boost::asio::const_buffer> &_out_buffers, con_connection_ptr _connection)
{

	const unsigned char *src = boost::asio::buffer_cast<const unsigned char*>(_in_buffer);

	const char *dst = NULL;
	unsigned int dst_len = 0;

	std::string error_text = "Success";
	bool has_packet_error = false;
	
	if (con_command::parse_packet(src, _bytes_transferred,  (const unsigned char**)(&dst), dst_len))
	{
	    if ((dst_len == 3) && (dst[2] == CONTROLLER_ID))
	    {
		    controller_id_ = CONTROLLER_ID(dst[0], dst[1]);
		    pult_ = false;
	    }
	    else if ((dst_len == 1) && (dst[0] == PULT_ID))
	    {
		    controller_id_ = 0;
		    pult_ = true;
	    }
	    else
	    {
		    has_packet_error = true;
		    error_text = "Invalid packet";
	    }
	}
	else
	{
		has_packet_error = true;
		error_text = "Invalid packet";
	}

	try
	{
		if (pult_)
		{
			_connection->pult_connection_ = true;

			unsigned char data[2] = {0x50, has_packet_error ? 'N' : 'Y' };

			build_packet(answer_.data(), data, 2, answer_.size());

			_out_buffers.push_back(boost::asio::buffer(answer_, answer_.size()-1));

			g_log.msg(MSG_NORMAL, format("connect: id=pult, status=%1").arg(error_text));

		}
		else
		{
			try
			{
				network_.create_controller_connection(controller_id_, _connection);
				std::string key;
					
				if (!sqlhandle_.create_controller_on_network(controller_id_, key))
				{
				  network_.remove_controller_connection(controller_id_);
				  throw std::logic_error("SQL error");
				}

				if (!key.empty())
				{
				  _connection->encoder_.load_key(key);
				  _connection->secure_connection_ = true;
				}
				
			  _connection->pult_connection_ = false;
			  _connection->controller_id_ = controller_id_;
			  _connection->need_remove_controller_ = true;
				
			}
			catch(std::exception &error)
			{
				error_text = error.what();
				has_packet_error = true;
				network_.remove_controller_connection(controller_id_);
			}
						
			unsigned char data[3];
			
			memset(data, 0, sizeof(data));
			
			if (dst)
			{
			  data[0] = dst[0];
			  data[1] = dst[1];
			}  
						
			data[2] =  has_packet_error ? 'N' : 'Y';
			
			memset(answer_.data(), 0x01, answer_.size());  

		    build_packet(answer_.data(), data, 3, answer_.size());
		    
		    if (_connection->secure_connection_)
		    {
		  		_connection->encoder_.encode(answer_.data(), answer_.size());
		    }
		    
			_out_buffers.push_back(boost::asio::buffer(answer_));

			g_log.msg(MSG_NORMAL, format("connect: id=%1, status=%2").num(controller_id_).arg(error_text));
		}

		return has_packet_error ? start_last_write : start_write;
	}
	catch(std::exception &e)
	{
		if (pult_)
		{
			g_log.msg(MSG_CRITICAL, format("invalid packet: id=pult"));
		}
		else
		{
			g_log.msg(MSG_CRITICAL, format("invalid packet: id=%1").num(controller_id_));
		}
		
		return start_last_write;
	}
}

//////////////////////////////////////////////////////////////////////
// get_status_command
//////////////////////////////////////////////////////////////////////

bool get_status_command::create_command(std::vector<boost::asio::const_buffer> &_out_buffers, con_connection_ptr _connection)
{
	try
	{
		unsigned char data = 0x20;

		build_packet(request_.data(), &data, 1, request_.size());

		if (_connection->secure_connection_)
		{
			_connection->encoder_.encode(request_.data(), request_.size());
		}

		_out_buffers.push_back(boost::asio::buffer(request_));

		return true;
	}
	catch(std::exception &e)
	{
		return false;
	}
}

bool get_status_command::need_read()
{
	return true;
}

con_command *get_status_command::get_next_command()
{
	if (need_sync_time_)
	{
		return new sync_time_command(network_, sqlhandle_, controller_id_);
	}
	else
	{
		return this;
	}
}

con_command::return_t get_status_command::process_request(boost::asio::const_buffer _in_buffer, size_t _bytes_transferred, std::vector<boost::asio::const_buffer> &_out_buffers, con_connection_ptr _connection)
{
	const unsigned char *src = boost::asio::buffer_cast<const unsigned char*>(_in_buffer);
	
	const con_state::status_packet_t *dst = NULL;
	unsigned int dst_len = 0;
	
	if (con_command::parse_packet(src, _bytes_transferred,  (const unsigned char**)(&dst), dst_len) && (dst_len >= sizeof(con_state::status_packet_t)))
	{
	  if (dst->sign_ == SIGN_GET_STATUS)
	  {
		  _connection->state_ = *dst;
		  sqlhandle_.set_controller_state(_connection->controller_id_, *dst);
		  
		  con_state state;
		  
		  state = *dst;
		  
		  try 
		  {
		  
			boost::posix_time::ptime p1(boost::posix_time::second_clock::local_time());
		  
			boost::posix_time::time_duration td = p1 - state.as_local_time();
			
//			g_log.msg(MSG_NORMAL, format("sync_time: %1, %2'").num(abs(td.total_seconds())).num(g_config.time_sync_diff_sec_));
		  
			if (abs(td.total_seconds()) > g_config.time_sync_diff_sec_)
			{
		  	  need_sync_time_ = true;
			}
			
		  }	
		  catch (std::out_of_range &_e)	
		  {
			need_sync_time_ = true;
			g_log.msg(MSG_NORMAL, format("con_command: id=%1, status='Invalid data/time: %2'").num(_connection->controller_id_).arg(_e.what()));
		  }
	  }
	  else
	  {
		  g_log.msg(MSG_NORMAL, format("connect: id=%1, status='Invalid packet sign: %2'").num(_connection->controller_id_).num(dst->sign_));
	  }
	}

	return start_timer;
}

get_status_command::get_status_command(controllers_network &_network, sqlhandle &_sqlhandle, con_state::controller_id _id):
			con_command::con_command(_network, _sqlhandle),
			need_sync_time_(false)

{
}

get_status_command::~get_status_command()
{
}

//////////////////////////////////////////////////////////////////////
// set_status_command
//////////////////////////////////////////////////////////////////////

set_status_command::set_status_command(controllers_network &_network, sqlhandle &_sqlhandle, con_state::controller_id controller_id, unsigned char _status)
	:get_status_command::get_status_command(_network, _sqlhandle, controller_id),
	 status_(_status)
{

}

set_status_command::~set_status_command()
{

}

bool set_status_command::create_command(std::vector<boost::asio::const_buffer> &_out_buffers, con_connection_ptr _connection)
{
	try
	{
		unsigned char data[2] = {CONTROLLER_GSM_TU, status_};

		build_packet(request_.data(), data, 2, request_.size());

		if (_connection->secure_connection_)
		{
			_connection->encoder_.encode(request_.data(), request_.size());
		}

		_out_buffers.push_back(boost::asio::buffer(request_));

		return true;
	}
	catch(std::exception &e)
	{
		return false;
	}
}

con_command *set_status_command::get_next_command()
{
	return new get_status_command(network_, sqlhandle_, controller_id_);
}

//////////////////////////////////////////////////////////////////////
// set_program_command
//////////////////////////////////////////////////////////////////////

set_program_command::set_program_command(controllers_network &_network, sqlhandle &_sqlhandle, con_state::controller_id controller_id, unsigned char _program)
	:get_status_command::get_status_command(_network, _sqlhandle, controller_id),
	 program_(_program)
{

}

set_program_command::~set_program_command()
{

}

bool set_program_command::create_command(std::vector<boost::asio::const_buffer> &_out_buffers, con_connection_ptr _connection)
{
	try
	{
		unsigned char data[2] = {CONTROLLER_START_PROG_NUM, program_};

		build_packet(request_.data(), data, 2, request_.size());

		if (_connection->secure_connection_)
		{
			_connection->encoder_.encode(request_.data(), request_.size());
		}

		_out_buffers.push_back(boost::asio::buffer(request_));

		return true;
	}
	catch(std::exception &e)
	{
		return false;
	}
}

con_command *set_program_command::get_next_command()
{
	return new get_status_command(network_, sqlhandle_, controller_id_);
}

//////////////////////////////////////////////////////////////////////
// pult_command
//////////////////////////////////////////////////////////////////////

pult_command::pult_command(controllers_network &_network, sqlhandle &_sqlhandle):
		con_command::con_command(_network, _sqlhandle)
{
}

con_command *pult_command::get_next_command()
{
	return this;
}

con_command::return_t pult_command::process_request(boost::asio::const_buffer _in_buffer, size_t _bytes_transferred, std::vector<boost::asio::const_buffer> & _out_buffers, con_connection_ptr _connection)
{
	const unsigned char *src = boost::asio::buffer_cast<const unsigned char*>(_in_buffer);
	std::string error_text = "Success.";
	bool has_packet_error = false;

	const unsigned char *dst = NULL;
	unsigned int dst_len = 0;

	if (con_command::parse_packet(src, _bytes_transferred,  &dst, dst_len) && (dst_len >= 3))
	{
		unsigned int controller_id = CONTROLLER_ID(dst[0], dst[1]);
		unsigned char command = dst[2];

		switch (command)
		{
			case CONTROLLER_GET_STATUS:
			{
			  try
			  {
				con_state state;
				con_state::status_packet_t packet;

				memset(&packet, 0x0, sizeof(con_state::status_packet_t));

				if (network_.get_controller_state_by_id(controller_id, state))
				{
				  state.setup_packet(packet);
				}  

				build_packet(answer_status_.data(), (unsigned char*)&packet, sizeof(packet), answer_status_.size());
				_out_buffers.push_back(boost::asio::buffer(answer_status_));

			  }
			  catch (std::exception &e)
			  {
				has_packet_error = true;
				error_text = "Invalid controller number";
			  }
			}
			break;

			case CONTROLLER_GSM_TU:
			{
				try
				{
					network_.set_connection_command_by_id(controller_id, new set_status_command(network_, sqlhandle_, controller_id,  dst[3]));

					unsigned char data = COMMAND_ACCEPTED;

					build_packet(answer_tu_.data(), &data, 1, answer_tu_.size());
					_out_buffers.push_back(boost::asio::buffer(answer_tu_));

				}
				catch (std::exception &e)
				{
					has_packet_error = true;
					error_text = std::string("Controller error:") + e.what();
				}
			}
			break;

			case CONTROLLER_START_PROG_NUM:
			{
				try
				{
					network_.set_connection_command_by_id(controller_id, new set_program_command(network_, sqlhandle_, controller_id,  dst[3]));

					unsigned char data = COMMAND_ACCEPTED;

					build_packet(answer_tu_.data(), &data, 1, answer_tu_.size());
					_out_buffers.push_back(boost::asio::buffer(answer_tu_));

				}
				catch (std::exception &e)
				{
					has_packet_error = true;
					error_text = std::string("Controller error:") + e.what();
				}
			}
			break;

			case CONTROLLER_SYNC_TIME:
			{
				try
				{
					network_.set_connection_command_by_id(controller_id, new sync_time_command(network_, sqlhandle_, controller_id));

					unsigned char data = COMMAND_ACCEPTED;

					build_packet(answer_tu_.data(), &data, 1, answer_tu_.size());
					_out_buffers.push_back(boost::asio::buffer(answer_tu_));

				}
				catch (std::exception &e)
				{
					has_packet_error = true;
					error_text = std::string("Controller error:") + e.what();
				}
			}
			break;

			default:
			{
				has_packet_error = true;
				error_text = "Invalid packet ID.";
			}
		}
	}
	else
	{
		has_packet_error = true;
		error_text = "Invalid packet sign.";
	}

	if (has_packet_error)
	{
		unsigned char data = COMMAND_NOT_ACCEPTED;	// not_accepted

		build_packet(answer_tu_.data(), &data, 1, answer_tu_.size());
		_out_buffers.push_back(boost::asio::buffer(answer_tu_));
	}


	return start_write;
}

bool pult_command::create_command(std::vector<boost::asio::const_buffer> &_out_buffers, con_connection_ptr _connection)
{
	return false;		// only read from pult
}

pult_command::~pult_command()
{
}

bool pult_command::need_read()
{
	return true;
}

//////////////////////////////////////////////////////////////////////
// sync_time_command
//////////////////////////////////////////////////////////////////////

sync_time_command::sync_time_command(controllers_network &_network, sqlhandle &_sqlhandle, con_state::controller_id controller_id)
						:get_status_command::get_status_command(_network, _sqlhandle, controller_id)
{

}

sync_time_command::~sync_time_command()
{

}

con_command *sync_time_command::get_next_command()
{
	return new get_status_command(network_, sqlhandle_, controller_id_);
}

#define TOBCD(x)  (((x) / 10 << 4) | ((x) % 10))

bool sync_time_command::create_command(std::vector<boost::asio::const_buffer> &_out_buffers, con_connection_ptr _connection)
{
	typedef struct
	{
  		unsigned char sign_;
  		unsigned char rtc_hour_;
  		unsigned char rtc_min_;
  		unsigned char rtc_sec_;
  		unsigned char rtc_day_;
  		unsigned char rtc_month_;
  		unsigned char rtc_year_;
  		unsigned char rtc_day_of_week_;
	} __attribute__((__packed__)) time_data ;


	try
	{
		time_data data;

		data.sign_ = CONTROLLER_SYNC_TIME;

		time_t tim;

		time(&tim);

		struct tm tm1;

		localtime_r(&tim, &tm1);

		data.rtc_hour_ = TOBCD(tm1.tm_hour);
		data.rtc_min_ = TOBCD(tm1.tm_min);
		data.rtc_sec_ = TOBCD(tm1.tm_sec);
		data.rtc_day_ = TOBCD(tm1.tm_mday);
		data.rtc_month_ = TOBCD(tm1.tm_mon + 1);
		data.rtc_year_ = TOBCD(tm1.tm_year - 100);
		data.rtc_day_of_week_  = TOBCD( (tm1.tm_wday == 0) ? 6 : (tm1.tm_wday - 1));

		build_packet(request_.data(), (unsigned char*)&data, sizeof(data), request_.size());

		if (_connection->secure_connection_)
		{
			_connection->encoder_.encode(request_.data(), request_.size());
		}

		_out_buffers.push_back(boost::asio::buffer(request_));

		return true;
	}
	catch(std::exception &e)
	{
		return false;
	}

}

//////////////////////////////////////////////////////////////////////
// sync_time_command
//////////////////////////////////////////////////////////////////////

con_load_command::con_load_command(controllers_network &_network, sqlhandle &_sqlhandle, con_state::controller_id _id):
		con_command::con_command(_network, _sqlhandle),
		has_error_(false),
		stop_load_(false),
		controller_id_(_id)
{
}

con_load_command::~con_load_command()
{
}

bool con_load_command::init_prog()
{
	std::string program;

	return (sqlhandle_.get_controller_program(controller_id_, program) 
			&& prog_.init_from_base64(program) );
}

bool con_load_command::build_next_packet(std::vector<boost::asio::const_buffer> &_out_buffers, con_connection_ptr _connection, bool _repeat)
{
	try
	{
		con_load_command::program_chunk_struct_t chunk;

		chunk.id_ = CONTROLLER_WRITE_PROG;
		chunk.offs_ = prog_.get_offs();

		size_t len = 0;

		prog_.get_next_chunk(chunk.data_, sizeof(chunk.data_), len, _repeat);

		build_packet(answer_prog_.data(), (unsigned char*)&chunk, len + sizeof(chunk.id_) + sizeof(chunk.offs_) , answer_prog_.size());

		if (_connection->secure_connection_)
		{
			_connection->encoder_.encode(answer_prog_.data(), answer_prog_.size());
		}

		_out_buffers.push_back(boost::asio::buffer(answer_prog_));

		return true;
	}
	catch(std::exception &e)
	{
		return false;
	}
}


bool con_load_command::create_command(std::vector<boost::asio::const_buffer> &_out_buffers, con_connection_ptr _connection)
{
    repeat_count_ = 0;
    
    sqlhandle_.start_program_update(controller_id_);
    
	return build_next_packet(_out_buffers, _connection);
}

bool con_load_command::need_read()
{
	return true;
}

con_command *con_load_command::get_next_command()
{
	if (has_error_ || stop_load_)
	{
		if (has_error_)
		{
		  sqlhandle_.end_program_update(controller_id_, false);
		}
		
		return new get_status_command(network_, sqlhandle_, controller_id_);
	}
	else
	{
		return this;
	}
}

con_command::return_t con_load_command::process_request(boost::asio::const_buffer _in_buffer, size_t _bytes_transferred, std::vector<boost::asio::const_buffer> &_out_buffers, con_connection_ptr _connection)
{
	const unsigned char *src = boost::asio::buffer_cast<const unsigned char*>(_in_buffer);

	const unsigned char *dst = NULL;
	unsigned int dst_len = 0;

	if (con_command::parse_packet(src, _bytes_transferred,  &dst, dst_len) && (dst_len >= 1) && (dst[0] == 0))
	{
		repeat_count_ = 0;

		if (prog_.eof())
		{
		  sqlhandle_.end_program_update(controller_id_, true);
		  stop_load_ = true;
		  return con_command::start_timer;
		}
		
		if (build_next_packet(_out_buffers, _connection))
		{
			return con_command::start_write;
		}
	}
	else
	{
	    if (repeat_count_ < 3)
	    {
	  	  repeat_count_ ++;
 
		  if (build_next_packet(_out_buffers, _connection, true))
		  {
			return con_command::start_write;
		  }
	    }
	}

	has_error_ = true;
	return con_command::start_timer;		// command not recognized exit current command and start get status
}
